/*
 * Copyright (C) 2009,2010,2011  Paulo Cesar Pereira de Andrade.
 *
 * This is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * Authors:
 *	Paulo Cesar Pereira de Andrade
 */

#include "exl.h"

/* initial number of mp objects to cache, and increment size */
#define COUNT			16

/*
 * Prototypes
 */
static void
mpcache_check_z(void);

static void
mpcache_check_q(void);

static void
mpcache_check_r(void);

static void
mpcache_check_qq(void);

static void
mpcache_check_c(void);

static eint32_t
mpcache_sort_z(void);

static eint32_t
mpcache_sort_q(void);

static eint32_t
mpcache_sort_r(void);

static eint32_t
mpcache_sort_qq(void);

static eint32_t
mpcache_sort_c(void);

/*
 * Implementation
 */
void
eget_mpz(empz_t _z, eint32_t offset)
{
    empz_t	z;

    mpcache_check_z();
    mpz_set(z = thread_self->c_z.entries[thread_self->c_z.offset], _z);
    thread_self->c_z.indexes[thread_self->c_z.offset] = offset;
    ++thread_self->c_z.offset;
    thread_self->ess[offset].t = t_shz;
    thread_self->ess[offset].v.o = z;
}

void
eget_mpq(empq_t _q, eint32_t offset)
{
    empq_t	q;

    mpcache_check_q();
    mpq_set(q = thread_self->c_q.entries[thread_self->c_q.offset], _q);
    thread_self->c_q.indexes[thread_self->c_q.offset] = offset;
    ++thread_self->c_q.offset;
    thread_self->ess[offset].t = t_shq;
    thread_self->ess[offset].v.o = q;
}

void
eget_mpr(empr_t _r, eint32_t offset)
{
    empr_t	r;

    mpcache_check_r();
    empr_set(r = thread_self->c_r.entries[thread_self->c_r.offset], _r);
    thread_self->c_r.indexes[thread_self->c_r.offset] = offset;
    ++thread_self->c_r.offset;
    thread_self->ess[offset].t = t_shr;
    thread_self->ess[offset].v.o = r;
}

void
eget_cqq(ecqq_t _qq, eint32_t offset)
{
    ecqq_t	qq;

    mpcache_check_qq();
    cqq_set(qq = thread_self->c_qq.entries[thread_self->c_qq.offset], _qq);
    thread_self->c_qq.indexes[thread_self->c_qq.offset] = offset;
    ++thread_self->c_qq.offset;
    thread_self->ess[offset].t = t_sqq;
    thread_self->ess[offset].v.o = qq;
}

void
eget_mpc(empc_t _c, eint32_t offset)
{
    empc_t	c;

    mpcache_check_c();
    empc_set(c = thread_self->c_c.entries[thread_self->c_c.offset], _c);
    thread_self->c_c.indexes[thread_self->c_c.offset] = offset;
    ++thread_self->c_c.offset;
    thread_self->ess[offset].t = t_shc;
    thread_self->ess[offset].v.o = c;
}

void
empcache_setup(void)
{
    eint32_t	offset;

    enew_object((eobject_t *)&thread_self->c_z.entries,
		t_resize | t_void, sizeof(empcache_t *) * COUNT);
    enew_object((eobject_t *)&thread_self->c_z.indexes,
		t_resize | t_void, sizeof(eint32_t *) * COUNT);
    thread_self->c_z.length = COUNT;

    enew_object((eobject_t *)&thread_self->c_q.entries,
		t_resize | t_void, sizeof(empcache_t *) * COUNT);
    enew_object((eobject_t *)&thread_self->c_q.indexes,
		t_resize | t_void, sizeof(eint32_t *) * COUNT);
    thread_self->c_q.length = COUNT;

    enew_object((eobject_t *)&thread_self->c_r.entries,
		t_resize | t_void, sizeof(empcache_t *) * COUNT);
    enew_object((eobject_t *)&thread_self->c_r.indexes,
		t_resize | t_void, sizeof(eint32_t *) * COUNT);
    thread_self->c_r.length = COUNT;

    enew_object((eobject_t *)&thread_self->c_qq.entries,
		t_resize | t_void, sizeof(empcache_t *) * COUNT);
    enew_object((eobject_t *)&thread_self->c_qq.indexes,
		t_resize | t_void, sizeof(eint32_t *) * COUNT);
    thread_self->c_qq.length = COUNT;

    enew_object((eobject_t *)&thread_self->c_c.entries,
		t_resize | t_void, sizeof(empcache_t *) * COUNT);
    enew_object((eobject_t *)&thread_self->c_c.indexes,
		t_resize | t_void, sizeof(eint32_t *) * COUNT);
    thread_self->c_c.length = COUNT;

    for (offset = 0; offset < COUNT; offset++) {
	enew_object(thread_self->c_z.entries + offset, t_mpz, sizeof(mpz_t));
	mpz_init(thread_self->c_z.entries[offset]);
	enew_object(thread_self->c_q.entries + offset, t_mpq, sizeof(mpq_t));
	mpq_init(thread_self->c_q.entries[offset]);
	enew_object(thread_self->c_r.entries + offset, t_mpr, sizeof(mpfr_t));
	mpfr_init2(thread_self->c_r.entries[offset], thr_prc);
	enew_object(thread_self->c_qq.entries + offset, t_cqq, sizeof(cqq_t));
	cqq_init(thread_self->c_qq.entries[offset]);
	enew_object(thread_self->c_c.entries + offset, t_mpc, sizeof(mpc_t));
	mpc_init2(thread_self->c_c.entries[offset], thr_prc);
    }
}

static void
mpcache_check_z(void)
{
    eint32_t	i;
    eint32_t	l;

    if (thread_self->c_z.offset >= thread_self->c_z.length) {
	thread_self->c_z.offset = mpcache_sort_z();
	if (thread_self->c_z.offset >= thread_self->c_z.length) {
	    i = thread_self->c_z.length;
	    l = i + COUNT;
	    eresize_object((eobject_t *)&thread_self->c_z.entries,
			   sizeof(eobject_t) * l);
	    thread_self->c_z.length = l;
	    for (; i < l; i++) {
		enew_object(thread_self->c_z.entries + i,
			    t_mpz, sizeof(mpz_t));
		mpz_init(thread_self->c_z.entries[i]);
	    }
	}
    }
}

static void
mpcache_check_q(void)
{
    eint32_t	i;
    eint32_t	l;

    if (thread_self->c_q.offset >= thread_self->c_q.length) {
	thread_self->c_q.offset = mpcache_sort_q();
	if (thread_self->c_q.offset >= thread_self->c_q.length) {
	    i = thread_self->c_q.length;
	    l = i + COUNT;
	    eresize_object((eobject_t *)&thread_self->c_q.entries,
			   sizeof(eobject_t) * l);
	    thread_self->c_q.length = l;
	    for (; i < l; i++) {
		enew_object(thread_self->c_q.entries + i,
			    t_mpq, sizeof(mpq_t));
		mpq_init(thread_self->c_q.entries[i]);
	    }
	}
    }
}

static void
mpcache_check_r(void)
{
    eint32_t	i;
    eint32_t	l;

    if (thread_self->c_r.offset >= thread_self->c_r.length) {
	thread_self->c_r.offset = mpcache_sort_r();
	if (thread_self->c_r.offset >= thread_self->c_r.length) {
	    i = thread_self->c_r.length;
	    l = i + COUNT;
	    eresize_object((eobject_t *)&thread_self->c_r.entries,
			   sizeof(eobject_t) * l);
	    thread_self->c_r.length = l;
	    for (; i < l; i++) {
		enew_object(thread_self->c_r.entries + i,
			    t_mpr, sizeof(mpfr_t));
		mpfr_init2(thread_self->c_r.entries[i], thr_prc);
	    }
	}
    }
}

static void
mpcache_check_qq(void)
{
    eint32_t	i;
    eint32_t	l;

    if (thread_self->c_qq.offset >= thread_self->c_qq.length) {
	thread_self->c_qq.offset = mpcache_sort_qq();
	if (thread_self->c_qq.offset >= thread_self->c_qq.length) {
	    i = thread_self->c_qq.length;
	    l = i + COUNT;
	    eresize_object((eobject_t *)&thread_self->c_qq.entries,
			   sizeof(eobject_t) * l);
	    thread_self->c_qq.length = l;
	    for (; i < l; i++) {
		enew_object(thread_self->c_qq.entries + i,
			    t_cqq, sizeof(cqq_t));
		cqq_init(thread_self->c_qq.entries[i]);
	    }
	}
    }
}

static void
mpcache_check_c(void)
{
    eint32_t	i;
    eint32_t	l;

    if (thread_self->c_c.offset >= thread_self->c_c.length) {
	thread_self->c_c.offset = mpcache_sort_c();
	if (thread_self->c_c.offset >= thread_self->c_c.length) {
	    i = thread_self->c_c.length;
	    l = i + COUNT;
	    eresize_object((eobject_t *)&thread_self->c_c.entries,
			   sizeof(eobject_t) * l);
	    thread_self->c_c.length = l;
	    for (; i < l; i++) {
		enew_object(thread_self->c_c.entries + i,
			    t_mpc, sizeof(mpc_t));
		mpc_init2(thread_self->c_c.entries[i], thr_prc);
	    }
	}
    }
}

static eint32_t
mpcache_sort_z(void)
{
    eint32_t	 i;
    eint32_t	 j;
    eint32_t	*o;
    eobject_t	*v;
    eobject_t	 x;

    v = thread_self->c_z.entries;
    o = thread_self->c_z.indexes;
    i = 0;
    j = thread_self->c_z.length - 1;

    while (i < j) {
	/* while slot used */
	while (thread_self->ess[o[i]].t == t_shz &&
	       thread_self->ess[o[i]].v.o != v[i]) {
	    if (++i >= j)
		return (i);
	}

	/* while slot unused */
	while (thread_self->ess[o[j]].t != t_shz ||
	       thread_self->ess[o[j]].v.o == v[j]) {
	    if (--j <= i)
		return (i);
	}

	/* keep used slots in the start of the vector */
	if (i != j) {
	    x = v[i];
	    v[i] = v[j];
	    v[j] = x;
	    ++i;
	    --j;
	}
    }

    return (i);
}

static eint32_t
mpcache_sort_q(void)
{
    eint32_t	 i;
    eint32_t	 j;
    eint32_t	*o;
    eobject_t	*v;
    eobject_t	 x;

    v = thread_self->c_q.entries;
    o = thread_self->c_q.indexes;
    i = 0;
    j = thread_self->c_q.length - 1;

    while (i < j) {
	/* while slot used */
	while (thread_self->ess[o[i]].t == t_shq &&
	       thread_self->ess[o[i]].v.o != v[i]) {
	    if (++i >= j)
		return (i);
	}

	/* while slot unused */
	while (thread_self->ess[o[j]].t != t_shq ||
	       thread_self->ess[o[j]].v.o == v[j]) {
	    if (--j <= i)
		return (i);
	}

	/* keep used slots in the start of the vector */
	if (i != j) {
	    x = v[i];
	    v[i] = v[j];
	    v[j] = x;
	    ++i;
	    --j;
	}
    }

    return (i);
}

static eint32_t
mpcache_sort_r(void)
{
    eint32_t	 i;
    eint32_t	 j;
    eint32_t	*o;
    eobject_t	*v;
    eobject_t	 x;

    v = thread_self->c_r.entries;
    o = thread_self->c_r.indexes;
    i = 0;
    j = thread_self->c_r.length - 1;

    while (i < j) {
	/* while slot used */
	while (thread_self->ess[o[i]].t == t_shr &&
	       thread_self->ess[o[i]].v.o != v[i]) {
	    if (++i >= j)
		return (i);
	}

	/* while slot unused */
	while (thread_self->ess[o[j]].t != t_shr ||
	       thread_self->ess[o[j]].v.o == v[j]) {
	    if (--j <= i)
		return (i);
	}

	/* keep used slots in the start of the vector */
	if (i != j) {
	    x = v[i];
	    v[i] = v[j];
	    v[j] = x;
	    ++i;
	    --j;
	}
    }

    return (i);
}

static eint32_t
mpcache_sort_qq(void)
{
    eint32_t	 i;
    eint32_t	 j;
    eint32_t	*o;
    eobject_t	*v;
    eobject_t	 x;

    v = thread_self->c_qq.entries;
    o = thread_self->c_qq.indexes;
    i = 0;
    j = thread_self->c_qq.length - 1;

    while (i < j) {
	/* while slot used */
	while (thread_self->ess[o[i]].t == t_sqq &&
	       thread_self->ess[o[i]].v.o != v[i]) {
	    if (++i >= j)
		return (i);
	}

	/* while slot unused */
	while (thread_self->ess[o[j]].t != t_sqq ||
	       thread_self->ess[o[j]].v.o == v[j]) {
	    if (--j <= i)
		return (i);
	}

	/* keep used slots in the start of the vector */
	if (i != j) {
	    x = v[i];
	    v[i] = v[j];
	    v[j] = x;
	    ++i;
	    --j;
	}
    }

    return (i);
}

static eint32_t
mpcache_sort_c(void)
{
    eint32_t	 i;
    eint32_t	 j;
    eint32_t	*o;
    eobject_t	*v;
    eobject_t	 x;

    v = thread_self->c_c.entries;
    o = thread_self->c_c.indexes;
    i = 0;
    j = thread_self->c_c.length - 1;

    while (i < j) {
	/* while slot used */
	while (thread_self->ess[o[i]].t == t_shc &&
	       thread_self->ess[o[i]].v.o != v[i]) {
	    if (++i >= j)
		return (i);
	}

	/* while slot unused */
	while (thread_self->ess[o[j]].t != t_shc ||
	       thread_self->ess[o[j]].v.o == v[j]) {
	    if (--j <= i)
		return (i);
	}

	/* keep used slots in the start of the vector */
	if (i != j) {
	    x = v[i];
	    v[i] = v[j];
	    v[j] = x;
	    ++i;
	    --j;
	}
    }

    return (i);
}
